package com.example.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: 21050134
 * @date: 2023/11/2 9:40
 * @description:
 */
@Configuration
@Slf4j
public class ShiroConfig {

	//创建 ShiroFilterFactoryBean
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		//设置安全管理器
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		/*
	添加Shiro内置过滤器，常用的有如下过滤器：
	anon： 无需认证就可以访问
	authc： 必须认证才可以访问
	user： 如果使用了记住我功能就可以直接访问
	perms: 拥有某个资源权限才可以访问
	role： 拥有某个角色权限才可以访问
	*/
		Map<String,String> filterMap = new LinkedHashMap<String,String>();
		//filterMap.put("/user/*","authc");
		//授权过滤器
		filterMap.put("/toAdd","perms[user:add]"); //大家记得注意顺序！
		filterMap.put("/toUpdate","perms[user:update]");

		//设置注销过滤器
		filterMap.put("/logout","logout");
		/**
		 *  /** 匹配所有路劲
		 *  通过Map集合组成了一个拦截器链 ， 自项向下过速，一旦匹配，则不再执行下面的过滤如果下面的定义与上面冲突，那按照了谁先定义谁说了算所以/**一定要配置在最后
		 * 这里是否要对所有路径进行认证视情况而定，因为一些路由跳转可能在没登陆出现导致出错
		 */
		//filterMap.put("/**","authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //设置登录的页面
		shiroFilterFactoryBean.setLoginUrl("/toLogin");
		//设置未授权页面
		shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
		return shiroFilterFactoryBean;
	}

	//创建 DefaultWebSecurityManager
	@Bean(name = "securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm,
	                                                              @Qualifier("rememberMeManager") CookieRememberMeManager rememberMeManager,
	                                                              @Qualifier("mySessionManager") DefaultWebSessionManager webSessionManager,
	                                                              @Qualifier("ehCacheManager") EhCacheManager ehCacheManager){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//关联Realm
		securityManager.setRealm(userRealm);
		securityManager.setRememberMeManager(rememberMeManager);
		securityManager.setSessionManager(webSessionManager);
		//设置缓存管理
		//第一种缓存
		//securityManager.setCacheManager(cacheManager);
		//ehcache缓存，推荐
		securityManager.setCacheManager(ehCacheManager);

        return securityManager;
	}


	@Bean
	public CookieRememberMeManager rememberMeManager(){
		//cookie管理器
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		//cookie名字
		SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
		//设置有效期时间为30天
		simpleCookie.setMaxAge(259200);
		cookieRememberMeManager.setCookie(simpleCookie);
		//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
		cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        return cookieRememberMeManager;
	}



	//创建 realm 对象
    @Bean
	public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){
	    UserRealm myShiroRealm = new UserRealm();
        myShiroRealm.setCachingEnabled(true);
        //启用身份验证缓存，即缓存AuthenticationInfo信息，默认false
	    myShiroRealm.setAuthenticationCachingEnabled(true);
	    //缓存AuthenticationInfo 信息的缓存名称，在ehcache-shiro.xml 中有对应缓存的配置
	    myShiroRealm.setAuthenticationCacheName("authenticationCache");
	    //启用授权缓存，即缓存AuthorizationInfo 信息，默认false
	    myShiroRealm.setAuthorizationCachingEnabled(true);
	    //缓存AuthorizationInfo信息的缓存名称  在ehcache-shiro.xml中有对应缓存的配置
	    myShiroRealm.setAuthorizationCacheName("authorizationCache");
	    //myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);


	    //如果自定义了密码加密验证，则修改myRealm
	    //注意这里如果用了加密验证，则数据库一开始存的就是加密后的密码，然后也要对前端传过来的数据进行加密处理后再进行匹配验证
	    //myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);

		return myShiroRealm;
    }


	//配置ShiroDialect：方言，用于整合thymeleaf和shiro
	// 用于 thymeleaf 和 shiro 标签配合使用
	@Bean
	public ShiroDialect getShiroDialect(){
		return new ShiroDialect();
	}



	//解决Shiro第一次重定向url携带jsessionid问题
	@Bean
	public DefaultWebSessionManager mySessionManager(){
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		//将sessionIdUrlRewritingEnabled属性设置成false
		sessionManager.setSessionIdUrlRewritingEnabled(false);
		return sessionManager;
	}

	//  缓存配置
	//shiro自带的MemoryConstrainedCacheManager作缓存
	// 但是只能用于本机，在集群时就无法使用，需要使用ehcache
	@Bean(name = "cacheManager")
	public CacheManager cacheManager(){
		MemoryConstrainedCacheManager cacheManager = new MemoryConstrainedCacheManager();//使用内存缓存
		return cacheManager;
	}

	//配置ehcache,推荐使用
	@Bean(name = "ehCacheManager")
	public EhCacheManager ehCacheManager(){
		EhCacheManager ehCacheManager = new EhCacheManager();
		ehCacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
		return ehCacheManager;
	}


	/**
	 * 密码匹配凭证管理器
	 *
	 *  @return
	 */
	@Bean(name = "hashedCredentialsMatcher")
	public HashedCredentialsMatcher hashedCredentialsMatcher(){
		log.info("hashedCredentialsMatcher()");
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		// 散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		// 散列的次数，比如散列两次，相当于md5(md5(""));
		hashedCredentialsMatcher.setHashIterations(1024);
		return hashedCredentialsMatcher;

	}

	/**
	 * 开启shiro aop注解支持
	 * 使用代理方式;所以需要开启代码支持
	 * @param securityManager
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") DefaultWebSecurityManager securityManager){
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	/**
	 * 开启cglib代理
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator =new DefaultAdvisorAutoProxyCreator();
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

    //在用spring管理我们的类的时候有时候希望有些属性值是来源于一些配置文件，系统属性，或者一些方法调用的结果，
	// 对于前两种使用方式可以使用spring的PropertyPlaceholderConfigurer类来注入，
	// 对于后一种则可以使用org.springframework.beans.factory.config.MethodInvokingFactoryBean类来生成需要注入的bean的属性。
	// 通过MethodInvokingFactory Bean类，可注入方法返回值。
	// MethodInvokingFactoryBean用来获得某个方法的返回值，该方法既可以是静态方法，也可以是实例方法。
	// 该方法的返回值可以注入bean实例属性，也可以直接定义成bean实例
	//可查看http://blog.sina.com.cn/s/blog_72ef7bea0102wa0v.html
	/**
	 * 让某个实例的某个方法的返回值注入为Bean的实例
	 * Spring静态注入
	 * @param myShiroRealm
	 * @param rememberMeManager
	 * @param ehCacheManager
	 * @return
	 */
	@Bean(name = "methodInvokingFactoryBean")
	public MethodInvokingFactoryBean methodInvokingFactoryBean(
			@Qualifier("userRealm") UserRealm myShiroRealm,
			@Qualifier("rememberMeManager") CookieRememberMeManager rememberMeManager,
			@Qualifier("mySessionManager") DefaultWebSessionManager webSessionManager,
			@Qualifier("ehCacheManager")EhCacheManager ehCacheManager){
		MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
		factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
		factoryBean.setArguments(new Object[]{getDefaultWebSecurityManager(myShiroRealm, rememberMeManager,webSessionManager,ehCacheManager)});
		return factoryBean;
	}


}
